#include "zbacktrace.h"

#if ZUTIL_OSTYPE==ZUTIL_LINUX

#define _GNU_SOURCE
#define _XOPEN_SOURCE

#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <execinfo.h>

#if defined(REG_RIP)
# define SIGSEGV_STACK_IA64
# define REGFORMAT "%016lx"
#elif defined(REG_EIP)
# define SIGSEGV_STACK_X86
# define REGFORMAT "%08x"
#else
# define SIGSEGV_STACK_GENERIC
# define REGFORMAT "%x"
#endif

static const char * s_corefile = NULL;

static void rendertimer(FILE* fio)
{
	char buf[128];
	struct tm tm;
	time_t sec=time(NULL);
	localtime_r(&sec,&tm);
	strftime(buf, 100,"%Y/%m/%d %H:%M:%S",&tm);
	fprintf(fio,buf);
}
static void signal_segv(int signum, siginfo_t* info, void*ptr)
{
	static const char *si_codes[3] = {"", "SEGV_MAPERR", "SEGV_ACCERR"};
	size_t i;
	ucontext_t *ucontext = (ucontext_t*)ptr;
#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
	int f = 0;
	Dl_info dlinfo;
	void **bp = 0;
	void *ip = 0;
#else
	void *bt[20];
	char **strings;
	size_t sz;
#endif
	FILE* fio=NULL;
	if (s_corefile)
	{
		fio=fopen(s_corefile,"a+");
	}
	if (fio==NULL)
		fio=stderr;
	fprintf(fio, "====backtrace record at ");
	rendertimer(fio);
	fprintf(fio, "\nSegmentation Fault!\n");
	fprintf(fio, "info.si_signo = %d\n", signum);
	fprintf(fio, "info.si_errno = %d\n", info->si_errno);
	fprintf(fio, "info.si_code  = %d (%s)\n", info->si_code, si_codes[info->si_code]);
	fprintf(fio, "info.si_addr  = %p\n", info->si_addr);
/*	for(i = 0; i < NGREG; i++)
		fprintf(fio, "reg[%02d]       = 0x" REGFORMAT "\n", i, ucontext->uc_mcontext.gregs[i]);
*/
#if defined(SIGSEGV_STACK_X86) || defined(SIGSEGV_STACK_IA64)
# if defined(SIGSEGV_STACK_IA64)
	ip = (void*)ucontext->uc_mcontext.gregs[REG_RIP];
	bp = (void**)ucontext->uc_mcontext.gregs[REG_RBP];
# elif defined(SIGSEGV_STACK_X86)
	ip = (void*)ucontext->uc_mcontext.gregs[REG_EIP];
	bp = (void**)ucontext->uc_mcontext.gregs[REG_EBP];
# endif
	fprintf(fio, "Stack trace:\n");
	while(bp &amp;& ip)
	{
		if(!dladdr(ip, &dlinfo))
			break;
		const char *symname = dlinfo.dli_sname;
		fprintf(fio, "% 2d: %p <%s+%u> (%s)\n",++f,ip,symname,(unsigned)(ip - dlinfo.dli_saddr),dlinfo.dli_fname);
		if(dlinfo.dli_sname && !strcmp(dlinfo.dli_sname, "main"))
			break;
		ip = bp[1];
		bp = (void**)bp[0];  }
#else
	fprintf(fio, "Stack trace (non-dedicated):\n");
	sz = backtrace(bt, 20);
	strings = backtrace_symbols(bt, sz);
	for(i = 0; i < sz; ++i)
		fprintf(fio, "%s\n", strings[i]);
#endif
	fprintf(fio, "End of stack trace\n");
	exit (-1);
}

/* Obtain a backtrace and print it to stdout. */ 
void zGetBacktrace (char* buf, int maxsize)
{ 
	void *array[20]; 
	size_t size; 
	char **strings; 
	size_t i;
	size_t cur;

	size = backtrace (array, 20); 
	strings = backtrace_symbols (array, size);
 
	cur=snprintf(buf,maxsize,"Obtained %zd stack frames.\n", size); 
	if (cur>0)
	{
		buf+=cur;
		maxsize-=cur;
		for (i = 0; i < size; i++)
		{
			cur=snprintf(buf,maxsize,"%s\n", strings[i]);
			if (cur>0)
			{
				buf+=cur;
				maxsize-=cur;
			}
			else
				break;
		}
	}
	if (buf)
	{
		buf[0]='\0';
	}
	free (strings);
} 

int zEnableBacktrace(const char* cf)
{
	if (cf)
		s_corefile=cf;
	struct sigaction action;
	memset(&action, 0, sizeof(action));
	action.sa_sigaction = signal_segv;
	action.sa_flags = SA_SIGINFO;
	if(sigaction(SIGSEGV, &action, NULL) < 0)
	{
		perror("sigaction");
		return -1;
	}
	return 0;
}

void zSetBacktraceFile(const char* corefile)
{
	if (corefile)
		s_corefile=corefile;
}
#endif
